using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

using static Unity.Mathematics.math;

// 定义一个名为 GridVisualization 的结构体
// 该结构体负责将游戏中的“Grid（网格/地图）”以可视化的方式呈现出来
public struct GridVisualization
{
    // 常量定义：每个格子在可视化时由多少行、列的小块（blocks）组成
    public const int
        rowsPerCell = 7,       // 每个格子在垂直方向上由 7 行小块组成
        columnsPerCell = 5,    // 每个格子在水平方向上由 5 列小块组成
        blocksPerCell = rowsPerCell * columnsPerCell; // 每个格子总共由 7 * 5=35 个小块组成

    // Shader 中使用的属性 ID，用于传递位置和颜色数据给 GPU
    static int
        positionsId = Shader.PropertyToID("_Positions"), // 位置数据的 Shader 属性名对应的 ID
        colorsId = Shader.PropertyToID("_Colors");       // 颜色数据的 Shader 属性名对应的 ID

    // ComputeBuffer：用于在 CPU 和 GPU 之间高效传递大量数据（比如每个实例的位置和颜色）
    ComputeBuffer positionsBuffer, colorsBuffer;

    // NativeArray：Unity 的高性能原生容器，用于在 Job 系统中并行处理数据
    NativeArray<float3> positions,   // 每个小块的 3D 位置信息
                        colors,      // 每个小块的颜色信息
                        ripples;     // 用于实现涟漪（水波纹）效果的临时数据

    int rippleCount; // 当前记录的涟漪（点击反馈效果）数量

    Grid grid;       // 当前关联的游戏网格，包含地图数据，比如格子状态、尺寸等
    Material material; // 用于渲染的材质，上面绑定了 Shader，会接收位置和颜色 Buffer
    Mesh mesh;       // 渲染用的基础 Mesh，比如一个方块或者六边形片段

    /// <summary>
    /// 初始化可视化系统：创建 Buffer、生成初始位置/颜色数据、启动 Job 填充数据
    /// </summary>
    public void Initialize(Grid grid, Material material, Mesh mesh)
    {
        // 保存传入的引用
        this.grid = grid;
        this.material = material;
        this.mesh = mesh;

        // 计算总共需要渲染的小块数量：每个格子有 blocksPerCell 个小块，总共有 grid.CellCount 个格子
        int instanceCount = grid.CellCount * blocksPerCell;

        // 创建 NativeArray，用于在 CPU 端存储每个小块的位置和颜色信息
        positions = new NativeArray<float3>(instanceCount, Allocator.Persistent); // 持久化分配，直到手动释放
        colors = new NativeArray<float3>(instanceCount, Allocator.Persistent);
        ripples = new NativeArray<float3>(10, Allocator.Persistent); // 预分配 10 个涟漪效果的存储空间
        rippleCount = 0; // 当前没有涟漪效果

        // 创建 ComputeBuffer，用于将位置和颜色数据传递给 GPU
        positionsBuffer = new ComputeBuffer(instanceCount, 3 * 4); // 每个 float3 占 3 * 4 = 12 字节
        colorsBuffer = new ComputeBuffer(instanceCount, 3 * 4);

        // 将 Buffer 绑定到 Shader 的对应属性，让 Shader 可以读取这些数据
        material.SetBuffer(positionsId, positionsBuffer);
        material.SetBuffer(colorsId, colorsBuffer);

        // 启动一个并行 Job，用于初始化每个格子对应的小块的位置和颜色
        new InitializeVisualizationJob
        {
            positions = positions, // 输出位置数据
            colors = colors,       // 输出颜色数据
            rows = grid.Rows,      // 网格总行数
            columns = grid.Columns // 网格总列数
        }
        // 使用 ScheduleParallel 并行调度该 Job，按格子数量划分任务，每批处理 grid.Columns 个格子
        .ScheduleParallel(grid.CellCount, grid.Columns, default)
        .Complete(); // 等待该 Job 执行完成

        // 将 NativeArray 中的数据上传到 GPU 的 ComputeBuffer 中
        positionsBuffer.SetData(positions);
        colorsBuffer.SetData(colors);
    }

    /// <summary>
    /// 销毁时清理所有 NativeArray 和 ComputeBuffer，防止内存泄漏
    /// </summary>
    public void Dispose()
    {
        positions.Dispose();
        colors.Dispose();
        ripples.Dispose();
        positionsBuffer.Release();
        colorsBuffer.Release();
    }

    /// <summary>
    /// 每帧调用的绘制方法：如有涟漪动画则更新，然后调用 GPU Instancing 绘制所有小块
    /// </summary>
    public void Draw()
    {
        // 如果有活动的涟漪效果，先更新它们
        if (rippleCount > 0)
        {
            Update();
        }

        // 使用 Graphics.DrawMeshInstancedProcedural 高效绘制大量相同的 Mesh 实例
        // 每个实例的位置和颜色由 ComputeBuffer 传递
        Graphics.DrawMeshInstancedProcedural(
            mesh,                // 基础 Mesh（比如一个小方块）
            0,                   // 子网格索引
            material,            // 使用的材质
            new Bounds(Vector3.zero, Vector3.one), // 包围盒，用于视锥剔除
            positionsBuffer.count // 实例数量：即 positionsBuffer 中的数据条目数
        );
    }

    /// <summary>
    /// 更新涟漪动画效果：让涟漪随时间扩散，超出范围的则回收复用
    /// </summary>
    void Update()
    {
        float dt = Time.deltaTime; // 每帧时间间隔

        // 遍历所有当前的涟漪效果
        for (int i = 0; i < rippleCount; i++)
        {
            float3 ripple = ripples[i]; // 获取当前涟漪数据，可能是位置 + 动画进度 z
            if (ripple.z < 1f) // 如果动画还没播放完（z 代表动画进度，0~1）
            {
                // 增加动画进度，最大为 1
                ripple.z = Mathf.Min(ripple.z + dt, 1f);
                ripples[i] = ripple;
            }
            else // 如果动画播放完毕
            {
                // 复用最后一个涟漪槽位，相当于队列头部移除、尾部插入
                ripples[i] = ripples[--rippleCount];
                i -= 1; // 因为当前 i 位置的元素被替换了，需要再检查一次
            }
        }

        // 启动一个并行 Job，用于根据最新的格子状态、涟漪等信息更新每个小块的位置和颜色
        new UpdateVisualizationJob
        {
            positions = positions,
            colors = colors,
            ripples = ripples,
            rippleCount = rippleCount,
            grid = grid
        }
        // 并行调度，按格子数量处理，每批 grid.Columns 个
        .ScheduleParallel(grid.CellCount, grid.Columns, default)
        .Complete(); // 等待完成

        // 将更新后的 NativeArray 数据同步回 GPU Buffer
        positionsBuffer.SetData(positions);
        colorsBuffer.SetData(colors);
    }

    /// <summary>
    /// 检测鼠标/射线点击是否命中了某个格子，并在命中时添加涟漪效果
    /// </summary>
    /// <param name="ray">从摄像机发出的射线</param>
    /// <param name="cellIndex">如果命中，返回命中的格子索引；否则为未定义</param>
    /// <returns>是否命中了有效格子</returns>
    public bool TryGetHitCellIndex(Ray ray, out int cellIndex)
    {
        // 从射线起点沿着方向发射一条水平射线（模拟俯视角点击检测）
        Vector3 p = ray.origin - ray.direction * (ray.origin.y / ray.direction.y);

        // 将点击位置的 X 坐标转换为格子列坐标
        float x = p.x + columnsPerCell / 2 + 1.5f;
        x /= columnsPerCell + 1;
        x += (grid.Columns - 1) * 0.5f;
        int c = Mathf.FloorToInt(x); // 列索引

        // 将点击位置的 Z 坐标转换为格子行坐标（考虑奇偶行偏移，适用于六边形/蜂窝状布局）
        float z = p.z + rowsPerCell / 2f + 1.5f;
        z /= rowsPerCell + 1;
        z += (grid.Rows - 1) * 0.5f + (c & 1) * 0.5f - 0.25f;
        int r = Mathf.FloorToInt(z); // 行索引

        // 检查该 (r, c) 是否是有效的格子索引，并且点击位置是否落在格子范围内
        bool valid = grid.TryGetCellIndex(r, c, out cellIndex) &&
            x - c > 1f / (columnsPerCell + 1) &&
            z - r > 1f / (rowsPerCell + 1);

        // 如果命中有效，且涟漪槽位未满，则添加一个新的涟漪动画起点
        if (valid && rippleCount < ripples.Length)
        {
            ripples[rippleCount++] = float3(p.x, p.z, 0f); // 记录点击位置和动画初始进度
        }

        return valid; // 返回是否命中
    }
}